﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;
using System.Linq;

using alib.Collections;
using alib.Concurrency;
using alib.Debugging;
using alib.Enumerable;
using alib.Memory;

namespace agree
{
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public unsafe sealed partial class Unification :  IDisposable
	{
#if UNIF_V6 && UNIF_V7
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		ArrayTfs _write_tfs(Scratch* ps, int ix_outer = 1)
		{
			ArrayTfs atfs;
			var o = opts & (Tfs.Flags.DeleteArgs | Tfs.Flags.Restrict);
			if (o == 0)
			{
				atfs = _write_tfs_v6(ps, ix_outer);
				//Console.Write('6');
			}
			else
			{
				atfs = _write_tfs_v7(ps, ix_outer);
				//Console.Write('7');
				//Console.Write(atfs.EdgeCount + " ");
			}

			_cleanup();
			return atfs;
#if false

			ArrayTfs atfs6 = null, atfs7 = null;
#if UNIF_V6
#if UNIF_V7 && DEBUG
			saved_state ss = new saved_state(this);
#endif
			atfs6 = _write_tfs_v6(ps, ix_outer);
			Debug.Assert(atfs6.CorefCount <= c_corefs);
#if UNIF_V7 && DEBUG
			ss.Restore();
#endif
#endif

#if UNIF_V7
			atfs7 = _write_tfs_v7(ps, ix_outer);
#endif

#if UNIF_V6 && UNIF_V7
			int cc_p2 = atfs7.CorefCount;
			int count_p2 = (int)atfs7.EdgeCount;

			//_dbg_count_display(ps, count_p2, count_p3);

			int d = count_p2 - count_p3;
			if (d != 0)
			{
				Nop.X();
			}

			if (cc_p2 != atfs6.CorefCount)
				Nop.X();

			//if (!atfs.entries.Select(ate => ate.e_FlagsId).OrderBy(ef => (int)ef)
			//    .SequenceEqual(atfs2.entries.Select(ate => ate.e_FlagsId).OrderBy(ef => (int)ef)))
			//{
			//    var x1 = atfs.entries.Select(ate => ate.e_FlagsId).OrderBy(ef => (int)ef).ToArray();
			//    var x2 = atfs2.entries.Select(ate => ate.e_FlagsId).OrderBy(ef => (int)ef).ToArray();

			//    var d1 = Enumerable.Range(0, x1.Length)
			//            .Where(z => x1[z] != x2[z])
			//            .Select(z => String.Format("{0,3} {1,-20} {2}", z, Edge.TypeInfoFromFlag(tm, x1[z]), Edge.TypeInfoFromFlag(tm, x2[z])))
			//            .ToArray();
			//    Nop.X();
			//}
			//Console.Write("[{0} {1} {2}] ", cc_p2, c_corefs, cc_p2 - c_corefs);

			if (d != 0)
			{
				Nop.X();
			}
#endif

#if UNIF_V6 && UNIF_V7
			_cleanup();
#endif
#if UNIF_V6
			return atfs6;
#elif UNIF_V7
			return atfs7;
#endif
#endif
		}
#endif

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		/// UNIF_V7
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#if UNIF_V7
		[DebuggerBrowsable(DebuggerBrowsableState.Never)]
		arr_tfs_entry* pate, _pate;
		int _cact;
		int i_ate;
		int _ix_outer;

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		uint g1, g2;
		uint g3, g4, g5, g6;
		//const uint g1 = 0;
		//const uint g2 = 1;
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		ArrayTfs _write_tfs_v7(Scratch* ps, int ix_outer)
		{
			Debug.Assert(ps->forward == 0 && ps->m_src == TopmostMark);
			Debug.Assert((ps->f & Edge.Flag.EtmNonBareType) != 0);

			Edge.Flag f = ps->f;
			Edge top_edge = new Edge(f, TopmostMark);

			_ix_outer = ix_outer;
			g1 = gen + 1;
			g2 = gen + 2;
			g3 = gen + 3;
			g4 = gen + 4;
			g5 = gen + 5;
			g6 = gen + 6;
			next_mark = 2;
			next_coref_mark = -1;

			//Nop.CodeCoverage(ix_outer == 1);
			if (ix_outer == 1)
				Debugger.Break();
			if ((opts & Tfs.Flags.RootCoverage) != 0)
				Debugger.Break();
			if ((opts & Tfs.Flags.WantVarbits) != 0)
				Debugger.Break();

			int c_crf;
			ushort* pfix = *(ushort**)((long)rgpfix + ((int)f << 3));
			pinfo* pi = pbx + ps->tfsix;

			var o = opts & (Tfs.Flags.DeleteArgs | Tfs.Flags.Restrict);
			if (o == 0)
			{
				//_cact = count_p1 + (ActiveSlots.Count - c_init_nodes);
				c_crf = 0;
				throw new Exception();
			}
			else
			{
				_cact = _tfs_descent(pi, pfix, ps->m_src);
				c_crf = ~next_coref_mark;
			}

			arr_tfs_entry[] entries = new arr_tfs_entry[_cact > 256 ? _cact : 256];
			fixed (arr_tfs_entry* _p = entries)
			{
				_pate = pate = _p;// +c_crf;
				i_ate = 0;

				_tfs_write(pi, pfix, ps->m_src, TopmostMark);

				//Debug.Assert(pate - _p == _cact);
				Debug.Assert(i_ate == _cact);
			}

			ArrayTfs tfs = new ArrayTfs(tm, top_edge, entries, (uint)_cact, c_crf, opts);

#if !UNIF_V6
			_cleanup();
#endif
			return tfs;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void _tfs_write(pinfo* pi, ushort* pfix, int m_src, int m_dst)
		{
			Debug.Assert(m_src != 0);
			int ix;
			do
			{
				if ((ix = pi->_fetch_ix_only(*pfix, m_src)) == 0)
					continue;

				if (pi->tfsix == 1 && ix == _ix_outer)
				{
					Debug.Assert((opts & Tfs.Flags.DeleteArgs) == 0);
					_fwd_unif_write(*pfix, m_dst, (Scratch*)((long)ps_base + ((pi->uix + ix) << SHL_PS)));
					continue;
				}

				_switch_to_tfs(pi, ix, (short)*pfix, (short)m_dst);
			}
			while (*++pfix != 0xFFFF);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void _switch_to_tfs(pinfo* pi, int ix, short i_feat, short m_dst)
		{
			int nm = 0;
			Edge.Flag nf;
			Edge ne = pi->entries[ix - 1].e;
			bool f_descend = (ne.FlagsId & Edge.Flag.EtmNonBareType) != 0;
			Scratch* nps;

			if (ne.Mark >= 0)
				nf = ne.FlagsId;
			else if ((nps = *(pi->coref_designations - ne.Mark))->gen == g1)
				nf = ne.FlagsId & ~Edge.Flag.Coreference;
			else if (nps->gen == g2)
			{
				if (f_descend && ix != (nps - ps_base) - pi->uix)
					f_descend = false;

				nm = nps->ix_upper;
				nf = ne.FlagsId;
			}
			else
			{
				_fwd_unif_write(i_feat, m_dst, nps);
				return;
			}

			if (f_descend)
			{
				if (nm == 0)
					nm = next_mark++;
				_tfs_write(pi, *(ushort**)((long)rgpfix + ((int)ne.FlagsId << 3)), ne.Mark, nm);
			}

			//Debug.Assert(i_ate == pate - _pate);
			Debug.Assert(i_ate < _cact);
			pate->e = new Edge(nf, nm);
			*(short*)pate = (short)i_feat;
			pate->mark = (short)m_dst;
			ushort* pus = pi->phf + (byte)(i_feat ^ m_dst);
			pate->next = *pus;
			i_ate++;
			*pus = (ushort)i_ate;
			pate++;
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void _unif_write(pinfo* pi, ushort* pfix, int m_src, int m_dst)
		{
			Scratch* nps;
			int ix;
			do
				if (*pfix == ifeat_restrict || (ix = pi->_fetch_ix_only(*pfix, m_src)) == 0)
					continue;
				else if ((nps = (Scratch*)((long)ps_base + ((ix + pi->uix) << SHL_PS)))->gen != gen && nps->gen <= g2)
					_switch_to_tfs(pi, ix, (short)*pfix, (short)m_dst);
				else
					_fwd_unif_write(*pfix, m_dst, nps);
			while (*++pfix != 0xFFFF);
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void _fwd_unif_write(int i_feat, int m_dst, Scratch* nps)
		{
			int fwd;
			while ((fwd = nps->forward) < 0)
				nps = (Scratch*)((long)ps_base - fwd);

			uint g = nps->gen;

			int nm = 0;
			Edge.Flag nf;

			if (g < g5)
			{
				if (g == g4)
				{
					nm = nps->ix_upper;
					nf = nps->f | Edge.Flag.Coreference;
					nps->gen = g5;
				}
				else
					nf = nps->f;

				if ((nf & Edge.Flag.EtmNonBareType) != 0)
				{
					if (nm == 0)
						nm = next_mark++;
					_unif_write(pbx + nps->tfsix, *(ushort**)((long)rgpfix + ((int)nf << 3)), nps->m_src, nm);
				}
			}
			else
			{
				nm = nps->ix_upper;
				nf = nps->f | Edge.Flag.Coreference;
			}

			//Debug.Assert(i_ate == pate - _pate);
			Debug.Assert(i_ate < _cact);

			pate->e = new Edge(nf, nm);
			*(short*)pate = (short)i_feat;
			pate->mark = (short)m_dst;
			ushort* pus = error --> (ushort*)((long)_pate + (((byte)(i_feat ^ m_dst) << 4) + 4));
			pate->next = *pus;
			i_ate++;
			*pus = (ushort)i_ate;
			pate++;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// The point of this function is that we don't need to maintain the expensive unifier structures when 
		/// completing the outer mother structure. Instead we just walk the mother TFS, keeping an eye out for 
		/// either a coreference or the daughter's topmost attachment, since these are the only ways to enter the
		/// (successfully unified) structure. When found, we switch to walking the unifier table instead.
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		int _tfs_descent(pinfo* pi, ushort* pfix, int m)
		{
			Debug.Assert(m != 0);
			Scratch* nps;
			int c = pfix[-1], ix;
			do
			{
				if ((ix = pi->_fetch_ix_only(*pfix, m)) == 0)
					c--;
				else if (pi->tfsix == 1 && ix == _ix_outer)
				{
					Debug.Assert((opts & Tfs.Flags.DeleteArgs) == 0);
					c += _unif_descent_node((Scratch*)((long)ps_base + ((pi->uix + ix) << SHL_PS)));
				}
				else
				{
					Edge ne = pi->entries[ix - 1].e;
					if (ne.Mark < 0)
					{
						Scratch* npsc;
						Scratch** pps;
						if ((npsc = *(pps = pi->coref_designations - ne.Mark)) != null)
						{
							if (npsc->gen == gen || npsc->gen == g3)
							{
								c += _unif_descent_node(npsc);
							}
							else if (npsc->gen == g1)
							{
								npsc->ix_upper = next_coref_mark--;
								npsc->gen = g2;
							}
							continue;
						}
						nps = (Scratch*)((long)ps_base + ((pi->uix + ix) << SHL_PS));
#if DEBUG
						nps->forward = 0;
#endif
						nps->gen = g1;
						*pps = nps;
					}
					if ((ne.FlagsId & Edge.Flag.EtmNonBareType) != 0)
						c += _tfs_descent(pi, *(ushort**)((long)rgpfix + ((int)ne.FlagsId << 3)), ne.Mark);
				}
			}
			while (*++pfix != 0xFFFF);
			return c;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		int _unif_descent_node(Scratch* nps)
		{
			int fwd;
			while ((fwd = nps->forward) < 0)
				nps = (Scratch*)((long)ps_base - fwd);

			Debug.Assert(nps->gen != g1 && nps->gen != g2);

			if (nps->gen < g3)
			{
				nps->gen = g3;
				if ((nps->f & Edge.Flag.EtmNonBareType) != 0)
					return _unif_descent(pbx + nps->tfsix, *(ushort**)((long)rgpfix + ((int)nps->f << 3)), nps->m_src);
			}
			else if (nps->gen < g4)
			{
				nps->ix_upper = next_coref_mark--;
				nps->gen = g4;
			}
			return 0;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		int _unif_descent(pinfo* pi, ushort* pfix, int m_src)
		{
			Scratch* nps,npsc;
			int c = pfix[-1], ix, fwd;
			do
				if (*pfix == ifeat_restrict)
					continue;
				else if ((ix = pi->_fetch_ix_only(*pfix, m_src)) == 0)
					c--;
				else if ((nps = (Scratch*)((long)ps_base + ((ix + pi->uix) << SHL_PS)))->gen >= gen)
					c += _unif_descent_node(nps);
				else
				{
					Edge ne;
					if ((ne = ((arr_tfs_entry*)((long)pi->entries + ((ix - 1) << 4)))->e).FlagsId < 0)
					{
						Scratch** pps;
						if ((npsc = *(pps = pi->coref_designations - ne.Mark)) != null)
						{
							if (npsc->gen == g1)
							{
								npsc->ix_upper = next_coref_mark--;
								npsc->gen = g2;
							}
							else if (npsc->gen != g2)
							{
								if ((fwd = npsc->forward) == 0)
									ix = (int)((long)ps_base - (long)npsc);
								else
									do
										npsc = (Scratch*)((long)ps_base - (ix = fwd));
									while ((fwd = npsc->forward) < 0);

								nps->forward = ix;
								nps->gen = gen;

								if (npsc->gen == g3)
								{
									npsc->ix_upper = next_coref_mark--;
									npsc->gen = g4;
								}
								else if (npsc->gen == gen)
									c += _unif_descent_node(npsc);
							}
							continue;
						}
						nps->gen = g1;
#if DEBUG
						nps->forward = 0;
#endif
						*pps = nps;
					}
					if ((ne.FlagsId & Edge.Flag.EtmNonBareType) != 0)
						c += _tfs_descent(pbx + pi->tfsix, *(ushort**)((long)rgpfix + ((int)ne.FlagsId << 3)), ne.Mark);
				}
			while (*++pfix != 0xFFFF);
			return c;
		}
#endif // V7_UNIF
#if P1_COUNT
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		int ResultEdgeCount(Scratch* ps, int ix_outer)
		{
			var o = opts & (Tfs.Flags.DeleteArgs | Tfs.Flags.Restrict);
			if (o == 0)
				return count_p1 + (ActiveSlots.Count - c_init_nodes);

			Nop.CodeCoverage(ix_outer == 1);

			Edge.Flag f = ps->f;
			if ((f & Edge.Flag.EtmNonBareType) == 0)
				return 0;
#if V7_UNIF
			return _tfs_descent(pbx + ps->tfsix, *(ushort**)((long)rgpfix + ((int)f << 3)), ps->m_src, ix_outer);
#else
			return -1;
#endif
		}
#endif

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// 
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		int _standalone_count()
		{
#if DEBUG
			return ActiveSlots.Sum(x =>
				{
					Slot* ps = (Slot*)x;
					ushort* _rgpfix = (*(ushort**)((long)(ps->_u.rgpfix) + ((int)ps->f << 3)));
					return ps->_count_func(this, _rgpfix);
				}) + ActiveSlots.Count - c_init_nodes;
#else
			return 0;
#endif
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public partial struct Slot
		{
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			/// 
			///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
			public int _count_func(Unification u, ushort* pfix)
			{
				if (forward != 0 || gen < u.gen)
					return 0;

				if ((f & Edge.Flag.EtmNonBareType) == 0)
					return 0;

				pinfo* pi = u.pbx + tfsix;
				//ushort* pfix = *(ushort**)((long)u.rgpfix + ((int)f << 3));
				ushort q = pfix[-1];
#if asymmetrical_counts
				do
				{
					int ix = pi->_fetch_ix_only(*pfix, m_src);
					if (ix == 0)
						return 0;
					if ((u.ps_base + (pi->uix + ix))->gen < gen)
						q--;
				}
				while (*++pfix != 0xFFFF);
#else
				int ix = pi->_fetch_ix_only(*pfix, m_src);
				if (ix == 0)
					return 0;

				if ((u.ps_base + (pi->uix0 + ix))->gen < gen)
					return 0;
#endif
				return q;
			}
		};
	};
};